home *** CD-ROM | disk | FTP | other *** search
/ Programming a Multiplayer FPS in DirectX / Programming a Multiplayer FPS in DirectX (Companion CD).iso / DirectX / dxsdk_oct2004.exe / dxsdk.exe / Samples / C++ / DirectInput / ActionBasic / ActionBasic.cpp next >
Encoding:
C/C++ Source or Header  |  2004-09-27  |  35.2 KB  |  914 lines

  1. //-----------------------------------------------------------------------------
  2. // File: ActionBasic.cpp
  3. //
  4. // Desc: ActionBasic Sample 
  5. //
  6. // Copyright (c) Microsoft Corporation. All rights reserved
  7. //-----------------------------------------------------------------------------
  8. #define STRICT
  9. #define DIRECTINPUT_VERSION 0x0800
  10.  
  11. #include <windows.h>
  12. #include <commctrl.h>
  13. #include <dinput.h>
  14. #include <string.h>
  15. #include <tchar.h>
  16. #include <minmax.h>
  17. #include "resource.h"
  18.  
  19.  
  20. //-----------------------------------------------------------------------------
  21. // Defines, and constants
  22. //-----------------------------------------------------------------------------
  23. // This GUID must be unique for every game, and the same for every instance of 
  24. // this app. The GUID allows DirectInput to remember input settings.
  25. // {3AFABAD0-D2C0-4514-B47E-65FEF9B5142F}
  26. const GUID g_guidApp = { 0x3afabad0, 0xd2c0, 0x4514, { 0xb4, 0x7e, 0x65, 0xfe, 0xf9, 0xb5, 0x14, 0x2f } };
  27.  
  28.  
  29. // Global constants
  30. const int MAX_DEVICES     = 8;    // The maximum number of allowed devices  
  31. const int LENGTH_DEV_NAME = 40;   // The maximum length of device names
  32. const int BUTTON_DOWN     = 0x80; // Mask for determining button state
  33. const int NUM_OF_ACTIONS  = 9;    // Number of game action constants
  34.  
  35.  
  36. // ****************************************************************************
  37. // Step 1: Define the game actions. 
  38. //         
  39. // One of the big advantages of using action mapping to assist with game input
  40. // is that it allows you to handle input in terms of game actions instead of
  41. // device objects. For instance, this sample defines a small set of actions
  42. // appropriate for a simple hand to hand combat game. When polling for user
  43. // input, the data can be handled in terms of kicks and punches instead of
  44. // button presses, axis movements, and keystrokes. This also allows to user
  45. // to customize the controls without any further effort on the part of the
  46. // developer.
  47. //
  48. // Each game action will be represented within your program as a 32 bit value.
  49. // For this sample, the game actions correspond to indices within an array, but
  50. // they could just as easily represent anything from variable addresses to
  51. // function pointers. The value you choose for each game action will be
  52. // returned by a call to GetDeviceData for triggered actions.
  53. // ****************************************************************************
  54.  
  55. // Game action constants
  56. // If you make changes to this list, make a corresponding change to the
  57. // NUM_OF_ACTIONS global constant.
  58. enum GAME_ACTIONS {
  59. WALK,               // Separate inputs are needed in this case for
  60. WALK_LEFT,          //   Walk/Left/Right because the joystick uses an
  61. WALK_RIGHT,         //   axis to report both left and right, but the
  62. BLOCK,              //   keyboard will use separate arrow keys.
  63. KICK, 
  64. PUNCH, 
  65. THE_DEAPPETIZER,    // "The De-Appetizer" represents a special move
  66. APOLOGIZE,          //   defined by this game.
  67. QUIT
  68. };
  69.  
  70. // Friendly names for action constants are used by DirectInput for the
  71. // built-in configuration UI
  72. const TCHAR *ACTION_NAMES[] =
  73. {
  74.     TEXT("Walk left/right"),
  75.     TEXT("Walk left"),
  76.     TEXT("Walk right"),
  77.     TEXT("Block"),
  78.     TEXT("Kick"),
  79.     TEXT("Punch"),
  80.     TEXT("\"The De-Appetizer\""),
  81.     TEXT("Apologize"),
  82.     TEXT("Quit")
  83. };
  84.  
  85. // ****************************************************************************
  86. // Step 2: Define the action map. 
  87. //         
  88. // The action map instructs DirectInput on how to map game actions to device
  89. // objects. By selecting a predefined game genre that closely matches our game,
  90. // you can largely avoid dealing directly with device details. For this sample
  91. // we've selected the DIVIRTUAL_FIGHTING_HAND2HAND, and this constant will need
  92. // to be selected into the DIACTIONFORMAT structure later to inform DirectInput
  93. // of our choice. Every device has a mapping from genre actions to device
  94. // objects, so mapping your game actions to genre actions almost guarantees
  95. // an appropriate device configuration for your game actions.
  96. //
  97. // If DirectInput has already been given an action map for this GUID, it
  98. // will have created a user map for this application 
  99. // (C:\Program Files\Common Files\DirectX\DirectInput\User Maps\*.ini). If a
  100. // map exists, DirectInput will use the action map defined in the stored user 
  101. // map instead of the map defined in your program. This allows the user to
  102. // customize controls without losing changes when the game restarts. If you 
  103. // wish to make changes to the default action map without changing the 
  104. // GUID, you will need to delete the stored user map from your hard drive
  105. // for the system to detect your changes and recreate a stored user map.
  106. // ****************************************************************************
  107.  
  108. // Map the game actions to the genre and keyboard constants.
  109. DIACTION g_adiaActionMap[] =
  110. {
  111.     // Device input (joystick, etc.) that is pre-defined by DInput according
  112.     // to genre type. The genre for this app is Action->Hand to Hand Fighting.
  113.     { WALK,             DIAXIS_FIGHTINGH_LATERAL,     0,  ACTION_NAMES[WALK], },
  114.     { BLOCK,            DIBUTTON_FIGHTINGH_BLOCK,     0,  ACTION_NAMES[BLOCK], },
  115.     { KICK,             DIBUTTON_FIGHTINGH_KICK,      0,  ACTION_NAMES[KICK], },
  116.     { PUNCH,            DIBUTTON_FIGHTINGH_PUNCH,     0,  ACTION_NAMES[PUNCH], },
  117.     { THE_DEAPPETIZER,  DIBUTTON_FIGHTINGH_SPECIAL1,  0,  ACTION_NAMES[THE_DEAPPETIZER], },
  118.  
  119.     // Map the apologize button to any button on the device. DirectInput
  120.     // defines several "Any-Control Constants" for mapping game actions to
  121.     // any device object of a particular type.
  122.     { APOLOGIZE,        DIBUTTON_ANY(1),              0,  ACTION_NAMES[APOLOGIZE], },
  123.  
  124.     // Keyboard input mappings
  125.     { WALK_LEFT,        DIKEYBOARD_LEFT,              0,  ACTION_NAMES[WALK_LEFT], },
  126.     { WALK_RIGHT,       DIKEYBOARD_RIGHT,             0,  ACTION_NAMES[WALK_RIGHT], },
  127.     { BLOCK,            DIKEYBOARD_B,                 0,  ACTION_NAMES[BLOCK], },
  128.     { KICK,             DIKEYBOARD_K,                 0,  ACTION_NAMES[KICK], },
  129.     { PUNCH,            DIKEYBOARD_P,                 0,  ACTION_NAMES[PUNCH], },
  130.     { THE_DEAPPETIZER,  DIKEYBOARD_D,                 0,  ACTION_NAMES[THE_DEAPPETIZER], },
  131.     { APOLOGIZE,        DIKEYBOARD_A,                 0,  ACTION_NAMES[APOLOGIZE], },
  132.  
  133.     // The DIA_APPFIXED constant can be used to instruct DirectInput that the
  134.     // current mapping can not be changed by the user.
  135.     { QUIT,             DIKEYBOARD_Q,      DIA_APPFIXED,  ACTION_NAMES[QUIT], },
  136.  
  137.     // Mouse input mappings
  138.     { WALK,             DIMOUSE_XAXIS,                0,  ACTION_NAMES[WALK], },
  139.     { PUNCH,            DIMOUSE_BUTTON0,              0,  ACTION_NAMES[PUNCH], },
  140.     { KICK,             DIMOUSE_BUTTON1,              0,  ACTION_NAMES[KICK], },
  141. };
  142.  
  143. inline DWORD GetNumOfMappings() { return sizeof(g_adiaActionMap)/sizeof(DIACTION); }
  144.  
  145.  
  146. // Convenience wrapper for device pointers
  147. struct DeviceState
  148. {
  149.     LPDIRECTINPUTDEVICE8 pDevice;   // Pointer to the device 
  150.     TCHAR szName[LENGTH_DEV_NAME];  // Friendly name of the device
  151.     bool  bAxisRelative;            // Relative x-axis data flag
  152.     DWORD dwInput[NUM_OF_ACTIONS];  // Arrays of the current input values and
  153.     DWORD dwPaint[NUM_OF_ACTIONS];  //   values when last painted
  154.     bool  bMapped[NUM_OF_ACTIONS];  // Flags whether action was successfully
  155. };                                  //   mapped to a device object
  156.  
  157.  
  158. // Global variables
  159. LPDIRECTINPUT8  g_pDI         = NULL;  // DirectInput access pointer
  160. DIACTIONFORMAT  g_diaf        = {0};   // DIACTIONFORMAT structure, used for
  161.                                        //   enumeration and viewing config
  162. HFONT           g_hActionFont = NULL;  // Defines the fonts to be used for
  163. HFONT           g_hDeviceFont = NULL;  //   drawing action and device names
  164. HWND            g_hDlg        = NULL;  // Handle to the dialog window
  165. bool            g_bRefChart   = TRUE;  // Flags a chart sizing refresh
  166. DeviceState     g_aDevices[MAX_DEVICES] = {0};  // List of devices
  167. int             g_iNumDevices = 0;     // Total number of stored devices
  168.  
  169.  
  170. // Function prototypes
  171. INT_PTR CALLBACK DialogProc( HWND, UINT, WPARAM, LPARAM );
  172. BOOL    CALLBACK EnumDevicesCallback( LPCDIDEVICEINSTANCE, LPDIRECTINPUTDEVICE8, DWORD, DWORD, LPVOID );
  173.  
  174. HRESULT InitDirectInput();
  175. void    PaintChart( HDC, RECT* );
  176. void    CheckInput();
  177. void    CreateFonts();
  178. void    DisplayHelp();
  179. void    Cleanup();
  180.  
  181.  
  182.  
  183.  
  184. //-----------------------------------------------------------------------------
  185. // Name: WinMain
  186. // Desc: Program entry point
  187. //-----------------------------------------------------------------------------
  188. int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, 
  189.                     LPSTR lpCmdLine, INT iCmdShow )
  190. {
  191.     InitCommonControls();
  192.  
  193.     // Create a modeless dialog box to serve as the user interface. A modeless
  194.     // dialog was selected so our message loop will run while the interface
  195.     // is displayed, which allows us to trap keyboard input.
  196.     CreateDialog( hInstance, MAKEINTRESOURCE(IDD_APPDIALOG), NULL, DialogProc );
  197.  
  198.  
  199.     MSG msg;
  200.  
  201.     // Background message loop
  202.     while( GetMessage( &msg, NULL, 0, 0) )
  203.     {
  204.         // Filter out unused keyboard input. Windows continues to handle
  205.         // keyboard messages even when the keyboard is acquired through
  206.         // DirectInput. 
  207.         if( msg.message == WM_KEYDOWN )
  208.         {
  209.             if( msg.wParam == VK_F1 )
  210.             {
  211.                 // Display the help screen for this sample
  212.                 DisplayHelp();
  213.             }
  214.             else if( msg.wParam == VK_ESCAPE )
  215.             {
  216.                 // Exit program. Let the dialog handle this
  217.             }
  218.             else
  219.             {
  220.                 // Block all other keyboard messages
  221.                 continue;
  222.             }
  223.         }
  224.        
  225.         if( !IsDialogMessage( g_hDlg, &msg ) )  
  226.         {
  227.             TranslateMessage( &msg );
  228.             DispatchMessage( &msg );
  229.         }
  230.  
  231.         if( msg.message == WM_QUIT )
  232.         {
  233.             DestroyWindow( g_hDlg );
  234.             break;
  235.         }
  236.     }
  237.  
  238.     Cleanup();
  239.     return 0;
  240. }
  241.  
  242.  
  243.  
  244.  
  245. //-----------------------------------------------------------------------------
  246. // Name: DialogProc
  247. // Desc: Handles dialog box messages
  248. //-----------------------------------------------------------------------------
  249. INT_PTR CALLBACK DialogProc( HWND hDlg, UINT message, 
  250.                              WPARAM wParam, LPARAM lParam )
  251. {
  252.     switch( message )
  253.     {
  254.         case WM_INITDIALOG:
  255.         {
  256.             // All initialization code is performed here
  257.  
  258.             g_hDlg = hDlg;
  259.  
  260.             CreateFonts();
  261.  
  262.             // Initialize DirectInput
  263.             if( FAILED(InitDirectInput()) )
  264.             {
  265.                 MessageBox( hDlg, TEXT("Failed to initialize DirectInput.\n")
  266.                                   TEXT("The sample will now exit."),
  267.                                   TEXT("Error"), MB_ICONEXCLAMATION | MB_OK );
  268.                 PostQuitMessage(0);
  269.             }
  270.  
  271.             // Start the timer. This timer is set to notify the dialog 30 times
  272.             // per second. Each timer tick signals that it's time to check
  273.             // for device input and refresh the interface.
  274.             SetTimer( hDlg, 1, 1000/30, NULL );
  275.             return TRUE;
  276.         }
  277.  
  278.         case WM_DRAWITEM:
  279.         {
  280.             // The action chart is drawn upon a win32 user-drawn button, and
  281.             // this event signals that all or part of the button needs to be
  282.             // redrawn due to a window event.
  283.             LPDRAWITEMSTRUCT pdis = (LPDRAWITEMSTRUCT) lParam;
  284.  
  285.             if( pdis->itemAction == ODA_DRAWENTIRE )
  286.                 PaintChart( pdis->hDC, &pdis->rcItem );
  287.  
  288.             return TRUE;
  289.         }
  290.  
  291.         case WM_COMMAND:
  292.         {    
  293.             // Handle button input from the user
  294.             switch( LOWORD(wParam) )
  295.             {
  296.                 case IDC_CONFIG:
  297.                 {
  298.                     // The user has clicked on the "View Configuration" button.
  299.                     // DirectInput has supports a native UI for viewing or
  300.                     // changing the current device mappings.
  301.                     DICONFIGUREDEVICESPARAMS diCDParams;
  302.  
  303.                     ZeroMemory( &diCDParams, sizeof(DICONFIGUREDEVICESPARAMS) );
  304.                     diCDParams.dwSize = sizeof(DICONFIGUREDEVICESPARAMS);
  305.                     diCDParams.dwcFormats = 1;
  306.                     diCDParams.lprgFormats = &g_diaf;
  307.                     diCDParams.hwnd = g_hDlg;
  308.  
  309.                     g_pDI->ConfigureDevices( NULL, &diCDParams, DICD_DEFAULT, NULL );
  310.                     break;
  311.                 }
  312.  
  313.                 case IDCANCEL:
  314.                 {
  315.                     PostQuitMessage( 0 );
  316.                     EndDialog( hDlg, 0 );
  317.                     return TRUE;
  318.                 }
  319.             }
  320.  
  321.             break;
  322.         }  
  323.  
  324.         case WM_TIMER:
  325.         {
  326.             CheckInput();
  327.             break;
  328.         }
  329.     }
  330.  
  331.     return FALSE;
  332. }
  333.  
  334.  
  335.  
  336.  
  337. //-----------------------------------------------------------------------------
  338. // Name: InitDirectInput
  339. // Desc: Initialize the DirectInput variables.
  340. //-----------------------------------------------------------------------------
  341. HRESULT InitDirectInput()
  342. {
  343.     HRESULT hr;
  344.  
  345.     // Register with the DirectInput subsystem and get a pointer to an 
  346.     // IDirectInput interface we can use.
  347.     if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
  348.                                          IID_IDirectInput8, (VOID**)&g_pDI, NULL ) ) )
  349.         return hr;
  350.  
  351.  
  352.     // ************************************************************************
  353.     // Step 3: Enumerate Devices.
  354.     // 
  355.     // Enumerate through devices according to the desired action map.
  356.     // Devices are enumerated in a prioritized order, such that devices which
  357.     // can best be mapped to the provided action map are returned first.
  358.     // ************************************************************************
  359.  
  360.     // Setup action format for the actual gameplay
  361.     ZeroMemory( &g_diaf, sizeof(DIACTIONFORMAT) );
  362.     g_diaf.dwSize          = sizeof(DIACTIONFORMAT);
  363.     g_diaf.dwActionSize    = sizeof(DIACTION);
  364.     g_diaf.dwDataSize      = GetNumOfMappings() * sizeof(DWORD);
  365.     g_diaf.guidActionMap   = g_guidApp;
  366.     g_diaf.dwGenre         = DIVIRTUAL_FIGHTING_HAND2HAND; 
  367.     g_diaf.dwNumActions    = GetNumOfMappings();
  368.     g_diaf.rgoAction       = g_adiaActionMap;
  369.     g_diaf.lAxisMin        = -99;
  370.     g_diaf.lAxisMax        = 99;
  371.     g_diaf.dwBufferSize    = 16;
  372.     _tcscpy( g_diaf.tszActionMap, _T("ActionMap Sample") );
  373.  
  374.     if( FAILED( hr = g_pDI->EnumDevicesBySemantics( NULL, &g_diaf,  
  375.                                                     EnumDevicesCallback,
  376.                                                     NULL, DIEDBSFL_ATTACHEDONLY ) ) )
  377.         return hr;
  378.  
  379.     return S_OK;
  380. }
  381.  
  382.  
  383.  
  384.  
  385. //-----------------------------------------------------------------------------
  386. // Name: EnumDevicesCallback
  387. // Desc: Callback function for EnumDevices. This particular function stores
  388. //       a list of all currently attached devices for use on the input chart.
  389. //-----------------------------------------------------------------------------
  390. BOOL CALLBACK EnumDevicesCallback( LPCDIDEVICEINSTANCE lpddi, 
  391.                                   LPDIRECTINPUTDEVICE8 lpdid, DWORD dwFlags, 
  392.                                   DWORD dwRemaining, LPVOID pvRef )
  393. {
  394.     HRESULT hr;
  395.  
  396.     if( g_iNumDevices < MAX_DEVICES )
  397.     {
  398.         // ********************************************************************
  399.         // Step 4: Build the action map against the device, inspect the
  400.         //         results, and set the action map.
  401.         //
  402.         // It's a good idea to inspect the results after building the action
  403.         // map against the current device. The contents of the action map
  404.         // structure indicate how and to what object the action was mapped. 
  405.         // This sample simply verifies the action was mapped to an object on
  406.         // the current device, and stores the result. Note that not all actions
  407.         // will necessarily be mapped to an object on all devices. For instance,
  408.         // this sample did not request that QUIT be mapped to any device other
  409.         // than the keyboard.
  410.         // ********************************************************************
  411.  
  412.    
  413.         // Build the action map against the device
  414.         if( FAILED(hr = lpdid->BuildActionMap( &g_diaf, NULL, DIDBAM_DEFAULT )) )
  415.             // There was an error while building the action map. Ignore this
  416.             // device, and contine with the enumeration
  417.             return DIENUM_CONTINUE;
  418.  
  419.         
  420.         // Inspect the results
  421.         for( UINT i=0; i < g_diaf.dwNumActions; i++ )
  422.         {
  423.             DIACTION *dia = &(g_diaf.rgoAction[i]);
  424.  
  425.             if( dia->dwHow != DIAH_ERROR && dia->dwHow != DIAH_UNMAPPED )
  426.                 g_aDevices[g_iNumDevices].bMapped[dia->uAppData] = TRUE;
  427.         }
  428.  
  429.         // Set the action map
  430.         if( FAILED(hr = lpdid->SetActionMap( &g_diaf, NULL, DIDSAM_DEFAULT )) )
  431.         {
  432.             // An error occured while trying the set the action map for the 
  433.             // current device. Clear the stored values, and continue to the
  434.             // next device.
  435.             ZeroMemory( g_aDevices[g_iNumDevices].bMapped, 
  436.                  sizeof(g_aDevices[g_iNumDevices].bMapped) );
  437.             return DIENUM_CONTINUE;
  438.         }
  439.  
  440.         // The current device has been successfully mapped. By storing the
  441.         // pointer and informing COM that we've added a reference to the 
  442.         // device, we can use this pointer later when gathering input.
  443.         g_aDevices[g_iNumDevices].pDevice = lpdid;
  444.         lpdid->AddRef();
  445.  
  446.         // Store the device's friendly name for display on the chart.
  447.         _tcsncat( g_aDevices[g_iNumDevices].szName, lpddi->tszInstanceName, LENGTH_DEV_NAME-5 );
  448.     
  449.         if( _tcslen( lpddi->tszInstanceName ) > LENGTH_DEV_NAME-5 )
  450.             _tcscat( g_aDevices[g_iNumDevices].szName, TEXT("...") );
  451.  
  452.         // Store axis absolute/relative flag
  453.         DIPROPDWORD dipdw;  
  454.         dipdw.diph.dwSize       = sizeof(DIPROPDWORD); 
  455.         dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
  456.         dipdw.diph.dwObj        = 0; 
  457.         dipdw.diph.dwHow        = DIPH_DEVICE; 
  458.  
  459.         hr = lpdid->GetProperty( DIPROP_AXISMODE, &dipdw.diph ); 
  460.         if (SUCCEEDED(hr)) 
  461.             g_aDevices[g_iNumDevices].bAxisRelative = ( DIPROPAXISMODE_REL == dipdw.dwData );
  462.  
  463.         g_bRefChart = TRUE; // Signal a chart resize
  464.         g_iNumDevices++;    // Increment the global device index   
  465.     }
  466.    
  467.     // Ask for the next device
  468.     return DIENUM_CONTINUE;
  469. }
  470.  
  471.  
  472.  
  473.  
  474. //-----------------------------------------------------------------------------
  475. // Name: CheckInput
  476. // Desc: Poll the attached devices and update the display
  477. //-----------------------------------------------------------------------------
  478. void CheckInput()
  479. {
  480.     HRESULT hr;
  481.     
  482.     // For each device gathered during enumeration, gather input. Although when
  483.     // using action maps the input is received according to actions, each device 
  484.     // must still be polled individually. Although for most actions your
  485.     // program will follow the same logic regardless of which device generated
  486.     // the action, there are special cases which require checking from which
  487.     // device an action originated.
  488.  
  489.     for( int iDevice=0; iDevice < g_iNumDevices; iDevice++ )
  490.     {
  491.         LPDIRECTINPUTDEVICE8 pdidDevice = g_aDevices[iDevice].pDevice;
  492.         DIDEVICEOBJECTDATA rgdod[10];
  493.         DWORD   dwItems = 10;
  494.  
  495.         hr = pdidDevice->Acquire();
  496.         hr = pdidDevice->Poll();
  497.         hr = pdidDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
  498.                                         rgdod, &dwItems, 0 );
  499.  
  500.         // GetDeviceData can fail for several reasons, some of which are
  501.         // expected during a program's execution. A device's acquisition is not
  502.         // permanent, and your program might need to reacquire a device several
  503.         // times. Since this sample is polling frequently, an attempt to
  504.         // acquire a lost device will occur during the next call to CheckInput.
  505.  
  506.         if( FAILED(hr) )
  507.             continue;
  508.  
  509.         // For each buffered data item, extract the game action and perform
  510.         // necessary game state changes. A more complex program would certainly
  511.         // handle each action separately, but this sample simply stores raw
  512.         // axis data for a WALK action, and button up or button down states for
  513.         // all other game actions. 
  514.  
  515.         // Relative axis data is never reported to be zero since relative data
  516.         // is given in relation to the last position, and only when movement 
  517.         // occurs. Manually set relative data to zero before checking input.
  518.         if( g_aDevices[iDevice].bAxisRelative )
  519.             g_aDevices[iDevice].dwInput[WALK] = 0;
  520.  
  521.         for( DWORD j=0; j<dwItems; j++ )
  522.         {
  523.             UINT_PTR dwAction = rgdod[j].uAppData;
  524.             DWORD dwData = 0;
  525.  
  526.             // The value stored in dwAction equals the 32 bit value stored in 
  527.             // the uAppData member of the DIACTION structure. For this sample
  528.             // we selected these action constants to be indices into an array,
  529.             // but we could have chosen these values to represent anything
  530.             // from variable addresses to function pointers.
  531.  
  532.             switch( dwAction )
  533.             {
  534.                 case WALK:  
  535.                 {
  536.                     // Axis data. Absolute axis data is already scaled to the
  537.                     // boundaries selected in the DIACTIONFORMAT structure, but
  538.                     // relative data is reported as relative motion change 
  539.                     // since the last report. This sample scales relative data
  540.                     // and clips it to axis data boundaries.
  541.  
  542.                     dwData = rgdod[j].dwData;   
  543.                     
  544.                     if( g_aDevices[iDevice].bAxisRelative )
  545.                     {
  546.                         // scale relative data
  547.                         dwData *= 5;
  548.  
  549.                         // clip to boundaries
  550.                         if( (int)dwData < 0 )
  551.                             dwData = max( (int)dwData, g_diaf.lAxisMin );
  552.                         else
  553.                             dwData = min( (int)dwData, g_diaf.lAxisMax );
  554.                     }
  555.                     
  556.                     break;
  557.                 }
  558.  
  559.                 default:
  560.                 {
  561.                     dwData = rgdod[j].dwData & BUTTON_DOWN ?    1 : 0;
  562.                     break;
  563.                 }
  564.             }
  565.  
  566.             g_aDevices[iDevice].dwInput[dwAction] = dwData;
  567.         }
  568.     }
  569.  
  570.     // Paint new data
  571.     HWND hChart = GetDlgItem( g_hDlg, IDCHART );
  572.     HDC hDC = GetDC( hChart );
  573.     PaintChart( hDC, NULL );
  574.     ReleaseDC( hChart, hDC );
  575. }
  576.  
  577.  
  578.  
  579.  
  580. //-----------------------------------------------------------------------------
  581. // Name: PaintChart
  582. // Desc: Output the device/action chart
  583. // Args: hDC - Device context handle
  584. //       prcWindow - Pointer to a RECT defining the window boundaries. If NULL,
  585. //                  the function will only draw cell changes. 
  586. //-----------------------------------------------------------------------------
  587. void PaintChart( HDC hDC, RECT *prcWindow )
  588. {   
  589.  
  590.     // Almost all of the code in this function is devoted to correctly
  591.     // positioning the chart, drawing labels, and minimizing flicker. The small
  592.     // portion of the code which deals with examining and painting the game
  593.     // data is clearly offset with comment lines.
  594.  
  595.     static const int GUTTER_SIZE     = 10;   // Chart display constants
  596.     static const int CELL_SIZE       = 15;
  597.  
  598.     static RECT rcChart = {0};  // Bounding RECT for the chart (grid and titles)
  599.     static RECT rcGrid  = {0};  // Bounding RECT for the grid
  600.  
  601.     int  iX=0, iY=0;             // Temp variables
  602.     RECT rc;
  603.  
  604.     HBRUSH hBrCell    = CreateSolidBrush( GetSysColor( COLOR_HIGHLIGHT ) );
  605.     HBRUSH hBrUnmap   = CreateHatchBrush( HS_BDIAGONAL, GetSysColor( COLOR_HIGHLIGHT ) );
  606.     HPEN   hPenGrid   = CreatePen( PS_SOLID, 0, GetSysColor( COLOR_BTNSHADOW ) );
  607.     HPEN   hPenAxis   = CreatePen( PS_SOLID, 0, GetSysColor( COLOR_BTNFACE ) );
  608.     POINT  aPoints[4] = {0};
  609.  
  610.     HBRUSH hFontOld   = (HBRUSH) GetCurrentObject( hDC, OBJ_FONT );
  611.     HPEN   hPenOld    = (HPEN) GetCurrentObject( hDC, OBJ_PEN );
  612.  
  613.     // Since this function can be called either when new data is available, or
  614.     // when a window event has caused a portion of the chart to become invalid,
  615.     // there are rare times when the labels and grid need to be painted. 
  616.     
  617.     // The  g_bRefChart flag is raised when the chart's size may have changed.
  618.     // The prcWindow RECT is filled when the chart's labels and grid must be
  619.     // painted.
  620.  
  621.     // Refresh chart (sizing and positioning)
  622.     if( g_bRefChart && prcWindow )
  623.     {
  624.         g_bRefChart = FALSE;
  625.  
  626.         int iMaxActionSize=0, iMaxDeviceSize=0;   
  627.         SIZE size;
  628.  
  629.         // Determine the largest action name size
  630.         SelectObject( hDC, g_hActionFont );
  631.         for( int i=0; i < NUM_OF_ACTIONS; i++ )
  632.         {
  633.             GetTextExtentPoint32( hDC, ACTION_NAMES[i], lstrlen( ACTION_NAMES[i] ), &size );
  634.             iMaxActionSize = max( size.cx, iMaxActionSize );
  635.         }
  636.  
  637.         // Determine the largest device name size
  638.         SelectObject( hDC, g_hDeviceFont );
  639.         for( int j=0; j < g_iNumDevices; j++ )
  640.         {
  641.             GetTextExtentPoint32( hDC, g_aDevices[j].szName, lstrlen( g_aDevices[j].szName ), &size );
  642.             iMaxDeviceSize = max( size.cx, iMaxDeviceSize );
  643.         }
  644.  
  645.         // Determine the bounding rectangle for the chart and grid
  646.         rcGrid.left    = iMaxDeviceSize + GUTTER_SIZE;
  647.         rcGrid.top     = iMaxActionSize + GUTTER_SIZE;
  648.  
  649.         rcChart.right  = rcGrid.left + ( CELL_SIZE * NUM_OF_ACTIONS );
  650.         rcChart.bottom = rcGrid.top  + ( CELL_SIZE * g_iNumDevices );
  651.         rcChart.left   = ( prcWindow->right  - rcChart.right  ) / 2;
  652.         rcChart.top    = ( prcWindow->bottom - rcChart.bottom ) / 2;
  653.  
  654.         rcChart.right  += rcChart.left;
  655.         rcChart.bottom += rcChart.top;
  656.  
  657.         rcGrid.left += rcChart.left;
  658.         rcGrid.top  += rcChart.top;
  659.         rcGrid.right = rcChart.right;
  660.         rcGrid.bottom = rcChart.bottom;
  661.     }
  662.  
  663.     // Repaint chart labels and grid
  664.     if( prcWindow )
  665.     {
  666.         SelectObject( hDC, g_hActionFont );
  667.         SelectObject( hDC, hPenGrid );
  668.  
  669.         iX = rcGrid.left;
  670.         iY = rcGrid.top - GUTTER_SIZE;
  671.  
  672.         for( int i=0; i < NUM_OF_ACTIONS; i++ )
  673.         {
  674.             TextOut( hDC, iX+2, iY, ACTION_NAMES[i], lstrlen(ACTION_NAMES[i]) );
  675.  
  676.             aPoints[0].x = iX;
  677.             aPoints[0].y = rcGrid.top;
  678.             aPoints[1].x = iX + CELL_SIZE;
  679.             aPoints[1].y = rcGrid.top;
  680.             aPoints[2].x = iX + CELL_SIZE;
  681.             aPoints[2].y = rcGrid.bottom;
  682.             aPoints[3].x = iX;
  683.             aPoints[3].y = rcGrid.bottom;
  684.             Polyline( hDC, aPoints, 4 );
  685.  
  686.             iX += CELL_SIZE;
  687.         }
  688.  
  689.         // Paint the legend
  690.         rc.left = 10;
  691.         rc.right = 19;
  692.  
  693.         SelectObject( hDC, g_hDeviceFont );
  694.         SelectObject( hDC, hPenGrid );
  695.  
  696.         // Inactive key 
  697.         TextOut(  hDC, 25,  8, TEXT("Inactive"), 8 );
  698.         MoveToEx( hDC,  8,  8, NULL );
  699.         LineTo(   hDC, 20,  8 );
  700.         LineTo(   hDC, 20, 20 );
  701.         LineTo(   hDC,  8, 20 );
  702.         LineTo(   hDC,  8,  8 );
  703.  
  704.         // Active key
  705.         TextOut(  hDC, 25, 24, TEXT("Active"), 6 );
  706.         MoveToEx( hDC,  8, 24, NULL );
  707.         LineTo(   hDC, 20, 24 );
  708.         LineTo(   hDC, 20, 36 );
  709.         LineTo(   hDC,  8, 36 );
  710.         LineTo(   hDC,  8, 24 );
  711.  
  712.         rc.top = 26;
  713.         rc.bottom = rc.top + 9;
  714.         FillRect( hDC, &rc, hBrCell );
  715.  
  716.         // Unmapped key
  717.         TextOut(  hDC, 25, 40, TEXT("Unmapped"), 8 );
  718.         MoveToEx( hDC,  8, 40, NULL );
  719.         LineTo(   hDC, 20, 40 );
  720.         LineTo(   hDC, 20, 52 );
  721.         LineTo(   hDC,  8, 52 );
  722.         LineTo(   hDC,  8, 40 );
  723.  
  724.         rc.top = 42;
  725.         rc.bottom = rc.top + 9;
  726.         FillRect( hDC, &rc, hBrUnmap );
  727.  
  728.     }
  729.  
  730.     SelectObject( hDC, g_hDeviceFont );
  731.     SetTextAlign( hDC, TA_RIGHT );
  732.     iY = rcGrid.top;
  733.  
  734.     // For each device, examine and paint the stored data.
  735.     for( int iDevice=0; iDevice < g_iNumDevices; iDevice++ )
  736.     {
  737.         if( prcWindow )
  738.         {
  739.             TextOut( hDC, rcGrid.left - GUTTER_SIZE, iY+1, g_aDevices[iDevice].szName, lstrlen( g_aDevices[iDevice].szName ) );
  740.            
  741.             aPoints[0].x = rcGrid.right;
  742.             aPoints[0].y = iY;
  743.             aPoints[1].x = rcGrid.left;
  744.             aPoints[1].y = iY;
  745.             aPoints[2].x = rcGrid.left;
  746.             aPoints[2].y = iY + CELL_SIZE;
  747.             aPoints[3].x = rcGrid.right;
  748.             aPoints[3].y = iY + CELL_SIZE;
  749.            
  750.             Polyline( hDC, aPoints, 4 );
  751.         }
  752.  
  753.         rc.top      = iY + 3;
  754.         rc.bottom   = rc.top + 10;
  755.         rc.left     = rcGrid.left + 3;
  756.         rc.right    = rc.left + 10;
  757.         
  758.  
  759.         // ____________________________________________________________________
  760.         // BEGIN paint stored data
  761.  
  762.         // Cycle through each game action
  763.         for( int i=0; i < NUM_OF_ACTIONS; i++ )
  764.         {
  765.             // If the action data has not changed since the last painting, we
  766.             // can skip to the next action to avoid flicker
  767.             if( prcWindow || g_aDevices[iDevice].dwInput[i] != g_aDevices[iDevice].dwPaint[i] )
  768.             {
  769.                 // If the current action was not mapped to an object on the current
  770.                 // device, select a cross-hatch brush into the DC. Else select
  771.                 // either a highlight or background colored brush, depending on
  772.                 // whether the current action is active.
  773.  
  774.                 HBRUSH hBrFill = NULL;
  775.                 if( !g_aDevices[iDevice].bMapped[i] )
  776.                     hBrFill = hBrUnmap;
  777.                 else if( g_aDevices[iDevice].dwInput[i] )
  778.                     hBrFill = hBrCell;
  779.                 else
  780.                     hBrFill = (HBRUSH) GetSysColorBrush( COLOR_BTNFACE );
  781.  
  782.                 // Fill the cell corresponding to the current device and action,
  783.                 // and store the current data to avoid unnecessary repainting.
  784.                 FillRect( hDC, &rc, hBrFill );
  785.                 g_aDevices[iDevice].dwPaint[i] = g_aDevices[iDevice].dwInput[i];
  786.  
  787.                 // For axis data, also draw an arrow to indicate the current
  788.                 // axis position. 
  789.                 if( i == WALK && g_aDevices[iDevice].dwInput[i] )
  790.                 {   
  791.                     // Scale the axis data to the size of the cell 
  792.                     int iTempX = rc.left+1 + (g_aDevices[iDevice].dwInput[i] + 100)/25; 
  793.  
  794.                     HPEN hPenPrev = (HPEN) SelectObject( hDC, hPenAxis );
  795.                     MoveToEx( hDC, iTempX,   rc.bottom-2, NULL );
  796.                     LineTo(   hDC, iTempX,   rc.top );
  797.                     MoveToEx( hDC, iTempX-1, rc.top+2,    NULL );
  798.                     LineTo(   hDC, iTempX+2, rc.top+2 );
  799.                     MoveToEx( hDC, iTempX-2, rc.top+3,    NULL );
  800.                     LineTo(   hDC, iTempX+3, rc.top+3 );
  801.                     SelectObject( hDC, hPenPrev );
  802.                 }
  803.             }
  804.  
  805.             // Advance the painting position over to the next action
  806.             rc.left += CELL_SIZE;
  807.             rc.right += CELL_SIZE;
  808.         }
  809.         // ____________________________________________________________________
  810.         // END paint stored data
  811.  
  812.         // Advance the painting position down to the next device
  813.         iY += CELL_SIZE;
  814.     }
  815.  
  816.     // Restore the DC to it's original state, and destroy created objects
  817.     SelectObject( hDC, hFontOld );
  818.     SelectObject( hDC, hPenOld );
  819.  
  820.     DeleteObject( hBrCell );
  821.     DeleteObject( hBrUnmap );
  822.     DeleteObject( hPenGrid );
  823.     DeleteObject( hPenAxis );
  824.  
  825. }
  826.  
  827.  
  828.  
  829.  
  830. //-----------------------------------------------------------------------------
  831. // Name: DisplayHelp
  832. // Desc: Displays the help screen
  833. //-----------------------------------------------------------------------------
  834. void DisplayHelp()
  835. {
  836.     MessageBox( g_hDlg, TEXT("The chart shows the list of devices found ")
  837.                         TEXT("attached to your computer, plotted against a\n")
  838.                         TEXT("defined set of actions for an imaginary fighting ")
  839.                         TEXT("game.\n\n")
  840.                         TEXT("30 times per second, the program polls for new ") 
  841.                         TEXT("input from the devices, and displays which actions\n")
  842.                         TEXT("are currently being sent by each device. During ")
  843.                         TEXT("initialization, the program attempts to establish\n")
  844.                         TEXT("a mapping between actions and device objects for ")
  845.                         TEXT("each attached device. Actions which were not\n")
  846.                         TEXT("mapped to a device object are shown as a ")
  847.                         TEXT("crosshatch-filled cell on the chart.\n\n")
  848.                         TEXT("To view the current action mappings for all the ")
  849.                         TEXT("devices, click the \"View Configuration\" button,\n")
  850.                         TEXT("which will access the default configuration UI ")
  851.                         TEXT("managed by DirectInput."),
  852.                         TEXT("ActionBasic Help"), MB_OK );
  853. }
  854.  
  855.  
  856.  
  857.  
  858. //-----------------------------------------------------------------------------
  859. // Name: CreateFonts
  860. // Desc: Creates the display fonts.
  861. //-----------------------------------------------------------------------------
  862. VOID CreateFonts()
  863. {
  864.     // Create display fonts. These fonts will be used to draw the action and
  865.     // device names on the chart.
  866.  
  867.     LOGFONT lf;
  868.     ZeroMemory( &lf, sizeof(LOGFONT) );
  869.     lf.lfHeight = 14;
  870.     lf.lfWeight = 500;
  871.     lf.lfCharSet = DEFAULT_CHARSET;
  872.     lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
  873.     _tcscpy( lf.lfFaceName, TEXT("arial") );
  874.  
  875.     g_hDeviceFont = CreateFontIndirect( &lf );
  876.  
  877.     lf.lfEscapement = 900;
  878.     lf.lfHeight = 12;
  879.     g_hActionFont = CreateFontIndirect( &lf );
  880. }
  881.  
  882.  
  883.  
  884.  
  885. //-----------------------------------------------------------------------------
  886. // Name: Cleanup
  887. // Desc: Release and clear all COM pointers
  888. //-----------------------------------------------------------------------------
  889. void Cleanup()
  890. {
  891.     // Release resources
  892.     DeleteObject( g_hDeviceFont );
  893.     DeleteObject( g_hActionFont );
  894.  
  895.     // Release device pointers
  896.     for( int i=0; i < g_iNumDevices; i++ )
  897.     {
  898.         if( g_aDevices[i].pDevice )
  899.         {
  900.             g_aDevices[i].pDevice->Unacquire();
  901.             g_aDevices[i].pDevice->Release();
  902.             g_aDevices[i].pDevice = NULL;
  903.         }
  904.     }
  905.  
  906.     // Release DirectInput
  907.     if( g_pDI )
  908.     {
  909.         g_pDI->Release();
  910.         g_pDI = NULL;
  911.     }
  912. }
  913.  
  914.